// FILE: Chip8CPU.cpp by Richard Hughes 28/04/2012
//
// DESC: Implements the Chip8 CPU

// INCLUDES
#include "Chip8CPU.h"
#include "Chip8RAM.h"
#include "Chip8Emulator.h"
#include "Interupt_ClearScreen.h"
#include "Interupt_UpdateScreen.h"
#include "Interupt_Buzzer.h"

// TODO: don't really want this... this is for DWORD timeGetTime()
// TODO: find or make this more platform indipendant - find out how to do this with the Android NDK
#include <Windows.h>

namespace Emulator
{
namespace Chip8
{

/*************************************************************/
//
// CLASS: Chip8CPU
//
// DESC: The Chip 8 CPU definition
//
/*************************************************************/

// constructor
// takes nothing
// returns nothing
Chip8CPU::Chip8CPU()
{
	for(int i = 0; i < 0xF; i++)
		this->Vx[i] = 0;

	this->I = 0;

	this->Timer = 0;
	this->Sound = 0;

	this->PC = BASE_WRITABLE;
	this->SP = 0;

	for(int i = 0; i < 16; i++)
		this->Stack[i] = 0;

	this->Stack[0] = BASE_WRITABLE; // ensure that if all returns happen, we go to the base of the code
}

// destructor
// takes nothing
// returns nothing
Chip8CPU::~Chip8CPU()
{
}

// init this emulator
// takes the opcode to execute
// returns nothing
uint16_t Chip8CPU::Tick(IRAM* ram)
{
	if(this->_lastFrameTime == 0)
	{
		this->_lastFrameTime = GetTickCount();
	}
	else
	{
		long time = GetTickCount();
		this->_currentFrameTime = time - this->_lastFrameTime;
		this->_lastFrameTime = time;
	}

	this->HandleTimer();
	this->HandleSound();

	uint16_t op = ram->Read16Bit(this->PC);
	this->PC += 2;

	uint8_t code = op >> 12;

	switch(code)
	{
	case 0x0:
		{
			this->Do0x0(op);
			break;
		}
	case 0x1:
		{
			this->Do0x1(op);
			break;
		}
	case 0x2:
		{
			this->Do0x2(op);
			break;
		}
	case 0x3:
		{
			this->Do0x3(op);
			break;
		}
	case 0x4:
		{
			this->Do0x4(op);
			break;
		}
	case 0x5:
		{
			this->Do0x5(op);
			break;
		}
	case 0x6:
		{
			this->Do0x6(op);
			break;
		}
	case 0x7:
		{
			this->Do0x7(op);
			break;
		}
	case 0x8:
		{
			this->Do0x8(op);
			break;
		}
	case 0x9:
		{
			this->Do0x9(op);
			break;
		}
	case 0xA:
		{
			this->Do0xA(op);
			break;
		}
	case 0xB:
		{
			this->Do0xB(op);
			break;
		}
	case 0xC:
		{
			this->Do0xC(op);
			break;
		}
	case 0xD:
		{
			this->Do0xD(op, ram);
			break;
		}
	case 0xE:
		{
			this->Do0xE(op);
			break;
		}
	case 0xF:
		{
			this->Do0xF(op, ram);
			break;
		}
	default:
		{
			// boom...
		}
	}; // end switch

	return op;
} // Tick

// these handle the respective opcodes
// they take the opcode to handle
// they return nothing
void Chip8CPU::Do0x0(uint16_t op)
{
	Chip8Screen* display = this->_emulator->GetDisplay();

	switch(op)
	{
	case 0x00E0: // CLS
		{
			Interupt_ClearScreen i;
			this->ActionInterupt(&i);
			break;
		}
	case 0x00EE: // RET
		{
			this->PC = this->Stack[this->SP];
			this->SP--;
			break;
		}
	case 0x00FB: // SCR
		{
			display->ScrollRight(this->_emulator->GetRunningMode());
			break;
		}
	case 0x00FC: // SCL
		{
			display->ScrollLeft(this->_emulator->GetRunningMode());
			break;
		}
	case 0x00FD: // EXIT
		{
			this->_emulator->Exit();
			break;
		}
	case 0x00FE: // LOW
		{
			this->_emulator->EnterChip8Mode();
			break;
		}
	case 0x00FF: // HIGH
		{
			this->_emulator->EnterSuperChip8Mode();
			break;
		}
	}

	// possibly some others...
	uint8_t code = (op >> 4) & 0x00F;

	switch(code)
	{
	case 0xC: // SCD nibble
		{
			uint8_t n = op & 0x000F;

			display->ScrollDown(n);
			break;
		}
	}
} // end Do0x0

void Chip8CPU::Do0x1(uint16_t op)
{
	// JP addr
	uint16_t addr = op & 0x0FFF;
	this->PC = addr;
} // end Do0x1

void Chip8CPU::Do0x2(uint16_t op)
{
	// CALL addr
	uint16_t addr = op & 0x0FFF;
	this->SP++;
	this->Stack[this->SP] = this->PC;
	this->PC = addr;
} // end Do0x2

void Chip8CPU::Do0x3(uint16_t op)
{
	// SE Vx, kk
	uint8_t x = (op >> 8) & 0x0F;
	uint8_t kk = op & 0x00FF;

	if(this->Vx[x] == kk)
		this->PC += 2;
} // end Do0x3

void Chip8CPU::Do0x4(uint16_t op)
{
	// SNE Vx, kk
	uint8_t x = (op >> 8) & 0x0F;
	uint8_t kk = op & 0x00FF;

	if(this->Vx[x] != kk)
		this->PC += 2;
} // end Do0x4

void Chip8CPU::Do0x5(uint16_t op)
{
	// SE Vx, Vy
	uint8_t x = (op >> 8) & 0x0F;
	uint8_t y = op & 0x000F;

	if(this->Vx[x] == this->Vx[y])
		this->PC += 2;
} // end Do0x5

void Chip8CPU::Do0x6(uint16_t op)
{
	// LD Vx, kk
	uint8_t x = (op >> 8) & 0x0F;
	uint8_t kk = op & 0x00FF;

	this->Vx[x] = kk;
} // end Do0x06

void Chip8CPU::Do0x7(uint16_t op)
{
	// ADD Vx, kk
	uint8_t x = (op >> 8) & 0x0F;
	uint8_t kk = op & 0x00FF;

	this->Vx[x] += kk;
} // end Do0x7

void Chip8CPU::Do0x8(uint16_t op)
{
	uint8_t instruction = op & 0x000F;

	switch(instruction)
	{
	case 0: // LD Vx, Vy
		{
			uint8_t x = (op >> 8) & 0x0F;
			uint8_t y = (op >> 4) & 0x00F;

			this->Vx[x] = this->Vx[y];
			break;
		}
	case 1: // OR Vx, Vy
		{
			uint8_t x = (op >> 8) & 0x0F;
			uint8_t y = (op >> 4) & 0x00F;

			this->Vx[x] |= this->Vx[y];
			break;
		}
	case 2: // AND Vx, Vy
		{
			uint8_t x = (op >> 8) & 0x0F;
			uint8_t y = (op >> 4) & 0x00F;

			this->Vx[x] &= this->Vx[y];
			break;
		}
	case 3: // XOR Vx, Vy
		{
			uint8_t x = (op >> 8) & 0x0F;
			uint8_t y = (op >> 4) & 0x00F;

			this->Vx[x] ^= this->Vx[y];
			break;
		}
	case 4: // ADD Vx, Vy
		{
			uint8_t x = (op >> 8) & 0x0F;
			uint8_t y = (op >> 4) & 0x00F;

			uint8_t v = this->Vx[x] + this->Vx[y];
			this->Vx[0xF] = v > 255 ? 1 : 0;
			this->Vx[x] = v & 0xFF; // only keep the lower 8 bits (if the value happens to be over 255)
			break;
		}
	case 5: // SUB Vx, Vy
		{
			uint8_t x = (op >> 8) & 0x0F;
			uint8_t y = (op >> 4) & 0x00F;

			this->Vx[0xF] = this->Vx[x] > this->Vx[y] ? 1 : 0;

			this->Vx[x] -= this->Vx[y];
			break;
		}
	case 6: // SHR Vx {, Vy}
		{
			uint8_t x = (op >> 8) & 0x0F;

			this->Vx[0xF] = (this->Vx[x] & 0x01) == 1 ? 1 : 0;

			this->Vx[x] /= 2;
			break;
		}
	case 7: // SUBN Vx, Vy
		{
			uint8_t x = (op >> 8) & 0x0F;
			uint8_t y = (op >> 4) & 0x00F;

			this->Vx[0xF] = this->Vx[y] > this->Vx[x] ? 1 : 0;

			this->Vx[x] = this->Vx[y] - this->Vx[x];
			break;
		}
	case 0xE: // SHL Vx {, Vy}
		{
			uint8_t x = (op >> 8) & 0x0F;

			this->Vx[0xF] = ((this->Vx[x] & 0x80) >> 7) == 1 ? 1 : 0;

			this->Vx[x] *= 2;
			break;
		}
	}
} // end Do0x8

void Chip8CPU::Do0x9(uint16_t op)
{
	// SNE Vx, Vy
	uint8_t x = (op >> 8) & 0x0F;
	uint8_t y = (op >> 4) & 0x00F;

	if(this->Vx[x] != this->Vx[y])
		this->PC += 2;
} // end Do0x9

void Chip8CPU::Do0xA(uint16_t op)
{
	// LD I, addr
	this->I = op & 0x0FFF;
} // end Do0xA

void Chip8CPU::Do0xB(uint16_t op)
{
	// JP V0, addr
	this->PC = (op & 0x0FFF) + this->Vx[0];
} // end Do0xB

void Chip8CPU::Do0xC(uint16_t op)
{
	// RND Vx, kk
	uint8_t x = (op >> 8) & 0x0F;
	uint8_t kk = op & 0x00FF;

	uint8_t r = rand() % 255;

	this->Vx[x] = r & kk;
} // end Do0xC

void Chip8CPU::Do0xD(uint16_t op, IRAM* ram)
{
	// DRW Vx, Vy, nibble
	uint8_t x = (op >> 8) & 0x0F;
	uint8_t y = (op >> 4) & 0x00F;
	uint8_t n = op & 0x000F;

	x = this->Vx[x];
	y = this->Vx[y];

	int spriteWidth = 8; // chip 8 sprites are always 8 pixels wide

	// SCHIP
	if(n == 0)
	{
		if(this->_emulator->GetRunningMode() == EmulatorRunningMode_Chip8)
			spriteWidth = 8;
		else
			spriteWidth = 16;

		n = 16 * 2; // SCHIP 8 sprites are 16 by 16 in HIGH mode so each line will need to be 16 bits wide (2 bytes) rather than 8 bits (1 byte)
	}

	uint8_t* sprite = new uint8_t[n];

	ram->ReadBytes(sprite, this->I, n);

	Chip8Screen* display = this->_emulator->GetDisplay();

	bool carry = false;
	for(int yPos = 0; yPos < n; yPos++)
	{
		int _y = y + yPos;

		_y = _y % display->GetHeight();

		uint8_t line = sprite[yPos];

		for(int xPos = 0; xPos < spriteWidth; xPos++)
		{
			int _x = x + xPos;

			_x = _x % display->GetWidth();

			bool on = false;
			if(xPos == 15)
			{
				on = (line & 0x01) > 0;
			}
			else if(xPos == 14)
			{
				on = (line & 0x02) > 0;
			}
			else if(xPos == 13)
			{
				on = (line & 0x04) > 0;
			}
			else if(xPos == 12)
			{
				on = (line & 0x08) > 0;
			}
			else if(xPos == 11)
			{
				on = ((line >> 4) & 0x1) > 0;
			}
			else if(xPos == 10)
			{
				on = ((line >> 4) & 0x2) > 0;
			}
			else if(xPos == 9)
			{
				on = ((line >> 4) & 0x4) > 0;
			}
			else if(xPos == 8)
			{
				line = sprite[yPos++]; // move to the second set of 8 bits for this line

				on = ((line >> 4) & 0x8) > 0;
			}
			else if(xPos == 7)
			{
				on = (line & 0x01) > 0;
			}
			else if(xPos == 6)
			{
				on = (line & 0x02) > 0;
			}
			else if(xPos == 5)
			{
				on = (line & 0x04) > 0;
			}
			else if(xPos == 4)
			{
				on = (line & 0x08) > 0;
			}
			else if(xPos == 3)
			{
				on = (line & 0x10) > 0;
			}
			else if(xPos == 2)
			{
				on = (line & 0x20) > 0;
			}
			else if(xPos == 1)
			{
				on = (line & 0x40) > 0;
			}
			else if(xPos == 0)
			{
				on = (line & 0x80) > 0;
			}

			if(on)
			{
				if(display->SetPixel(_x, _y))
				{
					carry = true;
				}
			}
			else
			{
				if(display->UnSetPixel(_x, _y))
				{
					carry = true;
				}
			}
		}
	}

	this->Vx[0xF] = carry ? 1 : 0;

	delete [] sprite;
	sprite = NULL;

	Interupt_UpdateScreen i;
	this->ActionInterupt(&i);
} // end Do0xD

void Chip8CPU::Do0xE(uint16_t op)
{
	uint8_t instruction = op & 0x00FF;

	switch(instruction)
	{
	case 0x9E: // SKP Vx
		{
			uint8_t x = (op >> 8) & 0x0F;

			Chip8Keyboard* keyboard = this->_emulator->GetKeyboard();

			if(keyboard->IsKeyDown((Chip8Key)x))
				this->PC += 2;
			break;
		}
	case 0xA1: // SKNP Vx
		{
			uint8_t x = (op >> 8) & 0x0F;

			Chip8Keyboard* keyboard = this->_emulator->GetKeyboard();

			if(keyboard->IsKeyUp((Chip8Key)x))
				this->PC += 2;
			break;
		}
	}
} // end Do0xE

void Chip8CPU::Do0xF(uint16_t op, IRAM* ram)
{
	uint8_t instruction = op & 0x00FF;

	switch(instruction)
	{
	case 0x07: // LD Vx, DT
		{
			uint8_t x = (op >> 8) & 0x0F;

			this->Vx[x] = this->Timer;
			break;
		}
	case 0x0A: // LD Vx, K
		{
			uint8_t x = (op >> 8) & 0x0F;

			Chip8Keyboard* keyboard = this->_emulator->GetKeyboard();

			byte key = keyboard->WaitUntilKeyPress();

			this->Vx[x] = key;
			break;
		}
	case 0x15: // LD DT, Vx
		{
			uint8_t x = (op >> 8) & 0x0F;

			this->Timer = this->Vx[x];
			break;
		}
	case 0x18: // LD ST, Vx
		{
			uint8_t x = (op >> 8) & 0x0F;

			this->Sound = this->Vx[x];
			break;
		}
	case 0x1E: // ADD I, Vx
		{
			uint8_t x = (op >> 8) & 0x0F;

			this->I += this->Vx[x];
			break;
		}
	case 0x29: // LD F, Vx
		{
			uint8_t x = (op >> 8) & 0x0F;

			x = this->Vx[x];

			address a = ((Chip8RAM*)ram)->GetFontStartAddress();
			this->I = a + (x * 5); // 5 bytes per font character
			break;
		}
	case 0x30: // LD HF, Vx
		{
			uint8_t x = (op >> 8) & 0x0F;
			break;
		}
	case 0x33: // LD B, Vx
		{
			uint8_t x = (op >> 8) & 0x0F;

			uint8_t v = this->Vx[x];

			ram->Write(this->I, (uint8_t)v / 100);

			v = v % 100; // get the tens which be after the decimal

			ram->Write(this->I + 1, (uint8_t)v / 10);

			v = v % 10; // get the units which be after the decimal

			ram->Write(this->I + 2, (uint8_t)v);

			break;
		}
	case 0x55: // LD [I], Vx
		{
			uint8_t x = (op >> 8) & 0x0F;

			for(int i = 0; i <= x; i++)
			{
				ram->Write(this->I + i, this->Vx[i]);
			}

			break;
		}
	case 0x65: // LD Vx, [I]
		{
			uint8_t x = (op >> 8) & 0x0F;

			for(int i = 0; i <= x; i++)
			{
				this->Vx[i] = ram->Read(this->I + i);
			}

			break;
		}
	case 0x75: // LD R, Vx
		{
			uint8_t x = (op >> 8) & 0x0F;
			break;
		}
	case 0x85: // LD Vx, R
		{
			uint8_t x = (op >> 8) & 0x0F;
			break;
		}
	}
} // end Do0xF

// actions the passed interupt
// the interupt to handle
// returns nothing
void Chip8CPU::ActionInterupt(IInterupt* interupt)
{
	if(interupt->GetType() == InteruptType_ClearScreen)
	{
		interupt->Action(this->_emulator->GetDisplay());
	}
	else if(interupt->GetType() == InteruptType_UpdateScreen)
	{
		interupt->Action(this->_emulator->GetDisplay());
	}
	else if(interupt->GetType() == InteruptType_Buzzer)
	{
		interupt->Action(this->_emulator->GetBuzzer());
	}
} // end ActionInterupt

// decresses the timer at a rate of 60hz
// takes nothing
// returns nothing
void Chip8CPU::HandleTimer()
{
	if(this->_currentFrameTime == 0 || this->Timer <= 0)
		return;

	static long _60hz = 1000 / 60;
	static long currentTime = 0;

	currentTime += this->_currentFrameTime;

	if(currentTime >= _60hz)
	{
		currentTime = 0;

		this->Timer--;
	} // end if
} // end HandleTimer

// decresses the sound at a rate of 60hz. If the sound is above 0, sends an interupt to sound the buzzer
// takes nothing
// returns nothing
void Chip8CPU::HandleSound()
{
	if(this->_currentFrameTime == 0 || this->Sound <= 0)
		return;

	static long _60hz = 1000 / 60;
	static long currentTime = 0;

	currentTime += this->_currentFrameTime;

	if(this->Sound > 0)
	{
		this->ActionInterupt(new Interupt_Buzzer(true));
	}
	else
	{
		this->ActionInterupt(new Interupt_Buzzer(false));
	}

	if(currentTime >= _60hz)
	{
		currentTime = 0;

		this->Sound--;
	} // end if
} // end HandleSound

} // end namespace Chip8
} // end namespace Emulator

// EOF: Chip8CPU.cpp